﻿/******************************************************************************
 * GSearchClient.cs
 * 
 * This module implements the GSearchClient class, which abstracts the process
 * of building the querystring and executing the verb against the service. It
 * returns a stream to its client either through the return value of the sync
 * call Search(), or in an event argument if SearchAsync() is used.
 * 
 * Date:   12/2008
 * 
 * Copyright (c) 2009, Mark Betz 
 * 
 * All rights reserved. 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met: 
 * 
 *   * Redistributions of source code must retain the above copyright 
 *     notice, this list of conditions and the following disclaimer. 
 *   * Redistributions in binary form must reproduce the above copyright 
 *     notice, this list of conditions and the following disclaimer in the 
 *     documentation and/or other materials provided with the distribution. 
 *   * Neither the name of the Author nor the names of contributors may be 
 *     used to endorse or promote products derived from this software
 *     without specific prior written permission. 
 *     
 * THIS SOFTWARE IS PROVIDED BY MARK BETZ ''AS IS'' AND ANY  EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL MARK BETZ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 * 
 */
using System;
using System.IO;
using System.Net;

namespace GSearch
{
    /// <summary>
    /// The arguments class for events raised by the GSearchClient
    /// </summary>
    public class SearchClientEventArgs : EventArgs
    {
        /// <summary>
        /// A System.Exception valid if there is an error event
        /// </summary>
        public Exception e { get; set; }
        
        /// <summary>
        /// A Stream object for accessing the results returned in a completion
        /// event
        /// </summary>
        public Stream result { get; set; }
    }
    
    /// <summary>
    /// The GSearchClient class abstracts the process of creating the service query
    /// and executing the search. It returns a stream to its client, which is of
    /// type GSearchCore, specialized for specific GResponse and GResults types.
    /// </summary>
    internal class GSearchClient 
    {
        /// <summary>
        /// The constructor creates the WebClient instance and wires up the OpenReadCompleted
        /// event for use in async calls.
        /// </summary>
        public GSearchClient(GSearchCore searcher)
        {
            if ( null == searcher )
                throw new ArgumentNullException("searcher", 
                    "The searcher argument to GSearchClient.GSearchClient() may not be null");
            _searcher = searcher;
            _wc = new WebClient();
            _wc.OpenReadCompleted += new OpenReadCompletedEventHandler(_wc_OpenReadCompleted);
        }
        
#if (!SILVERLIGHT)
        /// <summary>
        /// Search performs a blocking read from the Google ajax REST service. It
        /// retrieves the service uri from the searcher, and the querystring
        /// arguments from the args instance. This method is not available in the
        /// Silverlight version of the libraries.
        /// </summary>
        /// <param name="searcher">A searcher instance derived from GSearchCore</param>
        /// <param name="args">A set of search arguments derived from SearchArgs</param>
        /// <returns>Stream containing the results</returns>
        /// <exception cref="Exception">If an error occurs calling WebClient.OpenRead</exception>
        public Stream Search(GSearchCore searcher, SearchArgs args)
        {
            Uri uri = GetUri( searcher, args );
            Stream stream = null;
            
            try
            {
                stream = _wc.OpenRead( uri );
            }
            catch( WebException wex )
            {
                throw new Exception("Error during search", wex);
            }
            return stream;
        }
#endif
        
        /// <summary>
        /// SearchAsync performs a non-blocking read from the Google ajax REST
        /// service. It retrieves the service uri from the searcher and the query
        /// string arguments from the args instance. It uses WebClient, so results
        /// will be returned on the SearchComplete or SearchFailed events using the
        /// UI thread. Callers should wire these events before searching.
        /// </summary>
        /// <param name="searcher">A searcher instance derived from GSearchCore</param>
        /// <param name="args">A set of search arguments derived from SearchArgs</param>
        /// <exception cref="Exception">If an error occurs calling WebClient.OpenReadAsync</exception>
        public void SearchAsync(GSearchCore searcher, SearchArgs args)
        {
            Uri uri = GetUri( searcher, args );
            
            try
            {
                _wc.OpenReadAsync( uri );
            }
            catch( WebException wex )
            {
                throw new Exception("Error during search", wex );
            }
        }
        
        /// <summary>
        /// SearchComplete is raised when the service returns a complete response.
        /// The SearchEventArgs parameter to the event contains a reference to the
        /// resulting stream.
        /// </summary>
        public event EventHandler<SearchClientEventArgs> SearchComplete;
        
        /// <summary>
        /// SearchFailed is raised if there is an error during the execution of the
        /// search. The SearchEventArgs parameter to the event will contain a ref
        /// to the exception provided by WebClient.
        /// </summary>
        public event EventHandler<SearchClientEventArgs> SearchFailed;
        
        /// <summary>
        /// Raises the SearchComplete event
        /// </summary>
        /// <param name="e">SearchClientEventArgs to be passed to event subscribers</param>
        protected void OnSearchComplete( SearchClientEventArgs e )
        {
            if ( null != SearchComplete )
                SearchComplete( this, e );
        }
        
        /// <summary>
        /// Raises the SearchFailed event
        /// </summary>
        /// <param name="e">SearchClientEventArgs to be passed to event subscribers</param>
        protected void OnSearchFailed( SearchClientEventArgs e )
        {
            if ( null != SearchFailed )
                SearchFailed( this, e );
        }
        
        /// <summary>
        /// Called from Search and SearchAsync to construct the service uri
        /// </summary>
        /// <param name="searcher">searcher derived from GSearchCore</param>
        /// <param name="args">search arguments derived from SearchArgs</param>
        /// <returns>An instance of the Uri class</returns>
        private Uri GetUri( GSearchCore searcher, SearchArgs args )
        {
            string uriStr = searcher.ServiceURI;
            uriStr += args.ToQueryString();
            Uri uri = new Uri( uriStr, UriKind.Absolute );
            return uri;
        }
        
        /// <summary>
        /// Handles the OpenReadCompleted event of the WebClient
        /// </summary>
        /// <param name="sender">the WebClient generating the event</param>
        /// <param name="e">event arguments</param>
        private void _wc_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
        {
            SearchClientEventArgs ev = new SearchClientEventArgs();
            if (null == e.Error)
            {
                ev.e = null;
                ev.result = e.Result;
                OnSearchComplete( ev );    
            }
            else
            {
                ev.e = e.Error;
                ev.result = null;
                OnSearchFailed( ev );
            }
        }
        
        private WebClient _wc;
        private GSearchCore _searcher;
    }
}
